package jalgebrava.util.collections;

import jalgebrava.util.F;
import jalgebrava.util.Pair;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

/** 
 * Contains only public static final methods that take an iterable as first argument;
 * these could conceivably be used as extra methods on any Iterable. (e.g. Collection
 * E.g.:
 * IterableMixins.map(set, function) ==> set.map(function)
 * or with static imports:
 * map(set, function) ==> set.map(function)
 * <p> 
 * So it's not so bad :-)
 * However
 * filter(map(set, function), predicate) ==> set.map(function).filter(predicate) reads nicer.
 */

public final class IterableMixins {
	private static Logger logger = Logger.getLogger(IterableMixins.class);
	@SuppressWarnings("unused") // just there to keep EMMA happy
	private static IterableMixins instance = new IterableMixins();
	private IterableMixins() {
		// hidden
	}
	public static final <A,B> ImmutableIterable<B> map(Iterable<A> as, F<? super A,B> f) {		
		return mapIterable(as, f);		
	}
	public static final <A> ImmutableIterable<A> filter(final Iterable<A> as, final F<A,Boolean> predicate) {
		return filterIterable(as, predicate);
	}
	public static final <A> ImmutableIterable<A> concat(Iterable<A>... iterables) {
		
		final Iterator<A>[] iterators = new Iterator[iterables.length];
		int i=0;
		for (Iterable<A> iterable: iterables) {
			iterators[i] = iterable.iterator();
			i++;			
		}
		return new ImmutableIterable<A>() {

			@Override
			public ImmutableIterator<A> iterator() {
				return new ImmutableIterator<A>() {
					private int currentIteratorIndex = 0;
					@Override
					public boolean hasNext() {
						if (iterators[currentIteratorIndex].hasNext()) {
							return true;
						}
						for (int index=currentIteratorIndex + 1; index < iterators.length;index++) {
							if (iterators[index].hasNext()) {
								return true;
							}							
						}
						return false;
					}

					@Override
					public A next() {
						if (iterators[currentIteratorIndex].hasNext()) {
							return iterators[currentIteratorIndex].next();
						}
						while (!iterators[currentIteratorIndex].hasNext()) {
							currentIteratorIndex ++;
						}
						return iterators[currentIteratorIndex].next();
					}
					
				};
			}
			
		};
	}
	
	/**
	 * Makes sure the {@link Iterator#remove()} operation always fails
	 * @param <A> type of elements
	 * @param as iterable to make immutable
	 * @return immutable iterator
	 */
	public static final <A> ImmutableIterable<A> asImmutable(final Iterable<A> as) {
		return new ImmutableIterable<A>() {

			@Override
			public ImmutableIterator<A> iterator() {
				 
				return new ImmutableIterator<A>() {

					private final Iterator<A> it = as.iterator();
					
					@Override
					public boolean hasNext() {
						return it.hasNext();
					}

					@Override
					public A next() {
						return it.next();
					}
					
				};
			}

			@Override
			public String toString() {
				return as.toString();
			}
			
		};
		
	}
	private static final <A,B> ImmutableIterable<B> mapIterable(final Iterable<A> as, final F<? super A,B> f) {
		return new ImmutableIterable<B>() {
			Iterator<A> it = as.iterator();

			@Override
			public ImmutableIterator<B> iterator() {
				return new ImmutableIterator<B>() {

					@Override
					public boolean hasNext() {
						return it.hasNext();
					}
					@Override
					public B next() {
						return f.apply(it.next());
					}
					
					
				};
			}

			@Override
			public String toString() {
				return "mapped("+as.toString()+")";
			}
			
		};
	}

	public static final <A> List<A> makeList(Iterable<A> as) {
		ArrayList<A> result = new ArrayList<A>();
		for (A a: as) {
			result.add(a);
		}
		return result;		
	}
	
	private static final <A> ImmutableIterable<A> filterIterable(Iterable<A> as, final F<A,Boolean> predicate) {
		final Iterator<A> it = as.iterator();
		return new ImmutableIterable<A>() {

			@Override
			public ImmutableIterator<A> iterator() {
				return new ImmutableIterator<A>() {
 
					private A next = null;
					
					{
						advanceToNextSatisfying();
					}
					
					private void advanceToNextSatisfying() {

						while (it.hasNext()) {
							next = it.next();

							if (predicate.apply(next)) {

								return;
							}
						}
						next = null;
					}
					
					@Override
					public boolean hasNext() {
						return (next != null);
					}

					@Override
					public A next() {
						A result = next;
						advanceToNextSatisfying();
						return result;
					}
					
				};
			}
			
		};
	}
	
	public final static <A,B> ImmutableIterable<Pair<A,B>> zip(final Iterable<A> as, final Iterable<B> bs) {
		return new ImmutableIterable<Pair<A,B>>() {

			@Override
			public ImmutableIterator<Pair<A, B>> iterator() {
				final Iterator<A> itA = as.iterator();
				final Iterator<B> itB = bs.iterator();
				return new ImmutableIterator<Pair<A,B>>() {

					@Override
					public boolean hasNext() {
						return itA.hasNext() && itB.hasNext();
					}

					@Override
					public Pair<A, B> next() {
						return Pair.pair(itA.next(), itB.next());
					}
					
				};
			}
			
		};
	}
}
